{This file is part of CheLive.

CheLive is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar.  If not, see <http://www.gnu.org/licenses/>.}

{Реализует работу с OpenGL}
unit CoreOpenGL;

interface

uses
	SysUtils,
{$IFDEF WIN32}
	Windows,
{$ENDIF}
{$IFDEF LINUX}
	glx,
{$ENDIF}
	gl, glext, CoreTypes, CoreMath;

type
	{Характеристики видео-адаптера
	@member(exts строка расширений)
	@member(version версия)
	@member(vendor производитель)
	@member(renderer модель)
	@member(anisoMax макс. угол анизотропии)}
	TOpenGLCaps = record
		exts, version, vendor, renderer: String;
		anisoMax: TGLint;
	end;	
	
	{OpenGL
	@member(depthBits глубина буфера глубины)
	@member(stencilBits глубина стенцильного буфера)
	@member(canvas размеры холста для рисования (задаются в TOpenGL.Begin2D))
	@member(caps характеристики видео-адаптера)}
	TOpenGL = class
	{$IFDEF WIN32}
		{Контекст воспроизведения}
		rc: HGLRC;
	{$ENDIF}
	{$IFDEF LINUX}
		{Контекст воспроизведения}
		cont: TGLXContext;
	{$ENDIF}
		depthBits, stencilBits: Integer;
		canvas: TVec2;
		
		caps: TOpenGLCaps;
		
		{Инициализация (должна быть вызвана после TWindow.Init)
		@param(depth глубина буфера глубины)
		@param(stencil глубина стенцильного буфера)
		@returns(True если инициализация прошла успешно)}
		function Init(depth, stencil: Integer): Boolean;
		{Очистка буферов
		@param(color очистить цветовой буфер)
		@param(depth очистить буфер глубины)
		@param(stencil очистить стенцильный буфер)}
		procedure Clear(color: Boolean = True; depth: Boolean = True; stencil: Boolean = True);
		{Начать вывод 2D графики
		@param(canvasW ширина холста для рисования)
		@param(canvasH высота холста для рисования)}
		procedure Begin2D(canvasW: Single = 0; canvasH: Single = 0);
		{Окончить вывод 2D графики}
		procedure End2D;
		procedure Color(c: TVec3); overload;
		procedure Color(c: TVec4); overload;
		
		constructor Create;
		destructor Destroy; override;
	end;

var
	ogl: TOpenGL;

implementation

uses
	CoreConsole, CoreDisplay, CoreWindow;

constructor TOpenGL.Create;
begin
{$IFDEF WIN32}
	rc := 0;
{$ENDIF}
{$IFDEF LINUX}
	cont := nil;
{$ENDIF}
end;

destructor TOpenGL.Destroy;
begin
{$IFDEF WIN32}
	if rc <> 0 then
	begin
		wglMakeCurrent(wnd.dc, 0);
		wglDeleteContext(rc);
	end;
{$ENDIF}
{$IFDEF LINUX}
	if cont <> nil then
	begin
		glXMakeCurrent(disp.disp, wnd.wnd, nil);
		glXDestroyContext(disp.disp, cont);
	end;
{$ENDIF}
end;

procedure HandleEvents(event: PWindowEvent);
begin
	case event^.type_ of
		WND_SIZE: glViewport(0, 0, Round(event^.size.size.x), Round(event^.size.size.y));
	end;
end;

function TOpenGL.Init(depth, stencil: Integer): Boolean;
{$IFDEF WIN32}
var
	pfd: TPixelFormatDescriptor;
{$ENDIF}
begin
	Result := False;
	
{$IFDEF WIN32}
	FillChar(pfd, SizeOf(pfd), 0);
	
	with pfd do
	begin
		nSize := SizeOf(pfd);
		nVersion := 1;
		dwFlags := PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or PFD_DOUBLEBUFFER;
		iPixelType := PFD_TYPE_RGBA;
		cColorBits := 32;
		cDepthBits := depth;
		cStencilBits := stencil;
	end;
	
	if not SetPixelFormat(wnd.dc, ChoosePixelFormat(wnd.dc, @pfd), @pfd) then
	begin
		//error
		Exit;
	end;
	
	rc := wglCreateContext(wnd.dc);
	
	if rc = 0 then
	begin
		//error
		Exit;
	end;
	
	if not wglMakeCurrent(wnd.dc, rc) then
	begin
		//error
		Exit;
	end;
{$ENDIF}
{$IFDEF LINUX}
	cont := glXCreateContext(disp.disp, wnd.vi, nil, True);

	if cont = nil then
	begin
		//error
		Exit;
	end;

	if not glXMakeCurrent(disp.disp, wnd.wnd, cont) then
	begin
		//error
		Exit;
	end;
{$ENDIF}
	
	depthBits := depth;
	stencilBits := stencil;
	
	with caps do
	begin
		exts := glGetString(GL_EXTENSIONS);
		version := glGetString(GL_VERSION);
		vendor := glGetString(GL_VENDOR);
		renderer := glGetString(GL_RENDERER);
		
		con.Msg('*Версия: %s', [version]);
		con.Msg('*Производитель: %s', [vendor]);
		con.Msg('*Девайс: %s', [renderer]);
		//con.Msg('*Расширения: %s', [exts]);
	end;

	if not glext_ExtensionSupported('GL_ARB_multitexture', caps.exts) or
		not glext_ExtensionSupported('GL_EXT_framebuffer_object', caps.exts) or
		not glext_ExtensionSupported('GL_ARB_vertex_buffer_object', caps.exts) or
		not glext_ExtensionSupported('GL_ARB_shader_objects', caps.exts) or
		not glext_ExtensionSupported('GL_ARB_fragment_shader', caps.exts) or
		not glext_ExtensionSupported('GL_ARB_vertex_shader', caps.exts) or
		not glext_ExtensionSupported('GL_ARB_texture_cube_map', caps.exts) then
	begin
		con.Msg('#Выбростье ваш видео-адаптер в окно! %)');
		Exit;
	end else
	begin
		glext_LoadExtension('GL_ARB_multitexture');
		glext_LoadExtension('GL_EXT_framebuffer_object');
		glext_LoadExtension('GL_ARB_vertex_buffer_object');
		glext_LoadExtension('GL_ARB_shader_objects');
		glext_LoadExtension('GL_ARB_fragment_shader');
		glext_LoadExtension('GL_ARB_vertex_shader');
		glext_LoadExtension('GL_EXT_texture_filter_anisotropic');
	end;

	glViewport(0, 0, Round(wnd.clientSize.x), Round(wnd.clientSize.y));
	glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, @caps.anisoMax);
	glClearColor(0,0,0,0);
	glColor4f(1,1,1,1);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glClearDepth(1);
	glDepthFunc(GL_LEQUAL);
	
	Begin2D;
	End2D;
	
	wnd.AddProc(@HandleEvents);
	
	Result := True;
end;

procedure TOpenGL.Clear(color: Boolean = True; depth: Boolean = True; stencil: Boolean = True);
var
	flags: TGLenum;
begin
	flags := 0;
	
	if color then flags := flags or GL_COLOR_BUFFER_BIT; 
	if depth and (depthBits > 0) then flags := flags or GL_DEPTH_BUFFER_BIT;
	if stencil and (stencilBits > 0) then flags := flags or GL_STENCIL_BUFFER_BIT;
	
	glClear(flags);
end;

procedure TOpenGL.Begin2D(canvasW: Single = 0; canvasH: Single = 0);
var
	proj: TMat4;
begin
	if canvasW = 0 then canvasW := wnd.clientSize.x;
	if canvasH = 0 then canvasH := wnd.clientSize.y;
	
	canvas := Vec2(canvasW, canvasH);
	
	Mat4Ortho(@proj, canvasW, canvasH, -100, 100);
	
	glPushAttrib(GL_ENABLE_BIT or GL_COLOR_BUFFER_BIT);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	
	glMatrixMode(GL_PROJECTION);
	glPushMatrix;
	glLoadMatrixf(@proj);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix;
	glLoadIdentity;
end;

procedure TOpenGL.End2D;
begin
	glMatrixMode(GL_PROJECTION);
	glPopMatrix;
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix;
	
	glPopAttrib;
end;

procedure TOpenGL.Color(c: TVec3);
begin
	glColor3f(c.x, c.y, c.z);
end;

procedure TOpenGL.Color(c: TVec4);
begin
	glColor4f(c.x, c.y, c.z, c.w);
end;

end.
